#ifdef MSVC
#error Visual c++ is not supported
#endif
#include "stream_capture.h"
#include <stdio.h>
#include <io.h>
#include <windows.h>
#include <assert.h>
#include <fcntl.h>
#include <process.h>
#include "atomic_i386.h"

#define BUF_PIPE_SIZE 0x4000

static struct
{
    int fdpipe[2];/*read+write*/
    int old_fd;
    uintptr_t thr;  
    void (*handler)(void*,int);
}pipes[3];
static int OPT;
static void (*on_destroy)(void)=NULL;

#define Oem(num) (num ? CharToOemA(buf,buf) : OemToCharA(buf,buf))
#define PipeR(num) (num ? pipes[num].fdpipe[0]: pipes[num].old_fd)
#define PipeW(num) (num ? pipes[num].old_fd : pipes[num].fdpipe[1])

static int _io_read(void* buf,int buf_size,const int stream_num)//only for stdout,stderr
{   
    if ((buf_size<=1) || ((OPT & OPT_GUI) && !stream_num)) return 0;   
    int d = read(PipeR(stream_num),buf,buf_size-1);  
    if (d<1) return 0;
    if (OPT & OPT_DEBUG) assert(d<buf_size);
    
    if ((OPT & OPT_OEM ) && (!(OPT & OPT_GUI))) 
        {
        ((char*)buf)[d]=0;
        if (!Oem(stream_num)) return 0;
    }      
     
     
    return d;
    
}
static char _io_write(void* buf,int size,const int stream_num)
{
    if (size<=0) return 0;
    if ((OPT & OPT_GUI) && stream_num) return 0;
    return (write(PipeW(stream_num),buf,size)==size);
    
}
void set_destroy_event(void (*on_destroy_event)(void))
{
    if (!atomic_i386_is_locked2()) on_destroy=on_destroy_event;    
}
int read_master_stdin(void* buf,int buf_size)//deprecated in gui mode
{
    if (!pipes[0].handler) return 0;
    return _io_read(buf,buf_size,0);
    
}
int read_slave_stdout(void* buf,int buf_size)
{
    if (!pipes[1].handler) return 0;
    return _io_read(buf,buf_size,1);
    
}

int read_slave_stderr(void* buf,int buf_size)
{
    if (!pipes[2].handler) return 0;
    return _io_read(buf,buf_size,2);
}

char write_master_stdout(void* buf,int size)//deprecated in gui mode
{
    return _io_write(buf,size,1);
    
}
char write_master_stderr(void* buf,int size)//deprecated in gui mode
{
    return _io_write(buf,size,2);; 
    
}
char write_slave_stdin(void* buf,int size)
{
    return _io_write(buf,size,0);;
    
}

#define exec_handler(size) (*pipes[(size_t)param].handler)(buf,size)
static void io_do(void* const param)
{
  //  _endthread();
   char buf[BUF_PIPE_SIZE];int r,i;
   while (( r = _io_read(buf,sizeof(buf),(int)param))) 
   {
       if ((size_t)param && atomic_i386_is_locked2())       
           for (i=0;i<r;i++)
               if (!buf[i])
               {
                   exec_handler(i);
                   goto end_thr;
                   
               }
       
       exec_handler(r);       
   }
   end_thr:   
   _endthread();
   return;
}

/*volatile static int atomic_init;*/
static void init_stream(int num)
{
    
    /*if (atomic_init) return;    atomic_init++;maybe fix this*/
   
    
    if (!pipes[num].handler) return;
    FILE* streams[3];
    streams[0]=stdin;
    streams[1]=stdout;
    streams[2]=stderr;    
    assert(setvbuf(streams[num],NULL,_IONBF,0)==0);    
    assert(_pipe( pipes[num].fdpipe, sizeof(char[BUF_PIPE_SIZE]), O_BINARY )==0);
    assert(( pipes[num].old_fd=_dup(_fileno(streams[num])))!=-1);
    assert(_dup2(pipes[num].fdpipe[num!=0], _fileno(streams[num]))==0);    
    pipes[num].thr=_beginthread(io_do,0,(void*)num);
    
}
static void stream_destroy(void)
{
    int i;const char buf=0;
    atomic_i386_set_lock2();
    
  
    
    
    if (pipes[0].handler) TerminateThread((HANDLE)pipes[0].thr,0);/*unsafe code, maybe undefined behavior*/
#warning fix unsafe code
    
    for (i=1;i<3;i++)    
        if (pipes[i].handler)
        {
            
            if (write(pipes[i].fdpipe[1],&buf,sizeof(buf))==sizeof(buf))            
                WaitForSingleObject((HANDLE)pipes[i].thr,INFINITE);
            else
                {
                TerminateThread((HANDLE)pipes[i].thr,0);
                #warning fix unsafe code
                
                

            }
            
            
            
        }
    if (on_destroy)        (*on_destroy)();
    
    
      
    
   
    return;
   // int i;
    
   
    
}
int hook_init(int options,void (*handler_stdin)(void*,int),void (*handler_stdout)(void*,int),void (*handler_stderr)(void*,int))
{
    
    if (handler_stdout==handler_stderr && handler_stderr!=NULL) return 0;
    if (handler_stdin==handler_stderr && handler_stdin!=NULL) return 0;
    if (handler_stdin==handler_stdout && handler_stdout!=NULL) return 0;
    if (atomic_i386_is_lock()) return 0;   
    OPT=options;
   if (!(OPT & OPT_GUI)) pipes[0].handler=handler_stdin; else pipes[0].handler=NULL;
    pipes[1].handler=handler_stdout;
    pipes[2].handler=handler_stderr;
    int i;
    for (i=0;i<3;i++)
        init_stream(i);
    
    return (atexit(stream_destroy)==0);

  
}

